package com.open4cn.simulator.core.connector.serialport;

import com.open4cn.simulator.core.exception.IllegalParameterException;
import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author yinxiaopeng
 * @version 1.0.0
 * @company 致景科技
 * @department 飞梭BU/研发
 * @description
 * @Email yinxiaopeng@zj.tech
 * @date 2022-06-11 16:02
 */
public class SerialPortBootstrap {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    private int timeout = 60;
    private ExecutorService threadPool = defaultThreadPool();
    private SerialPortInitializer serialPortInitializer;

    /**
     * 打开串口,默认超时60秒
     *
     * @param comPort
     * @param baudRate
     * @return
     */
    public SerialPortChannel open(String comPort, Integer baudRate) {
        return open(comPort, baudRate, timeout);
    }


    /**
     * 打开串口
     *
     * @param comPort
     * @param baudRate
     * @param timeout
     * @return
     */
    public SerialPortChannel open(String comPort, Integer baudRate, int timeout) {
        check();
        try {
            //通过端口名识别端口
            CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(comPort);
            CommPort commPort = portIdentifier.open(comPort, timeout);
            //判断是不是串口
            if (commPort instanceof SerialPort) {
                return getSerialPortChannel((SerialPort) commPort, baudRate);
            }
        } catch (NoSuchPortException e) {
            logger.error("{}没有对应的串口设备", comPort, e);
        } catch (PortInUseException e) {
            logger.error("{}串口已被占用", comPort, e);
        }
        return null;
    }

    /**
     * 从串口获取channel
     *
     * @param serialPort
     * @param baudRate
     * @return
     */
    private SerialPortChannel getSerialPortChannel(SerialPort serialPort, Integer baudRate) {
        try {
            //设置串口的波特率等参数
            serialPort.setSerialPortParams(baudRate, DatebitsEnum.EIGHT.getCode(), StopbitsEnum.ONE.getCode(), ParityEnum.ZERO.getCode());
            //设置当有数据到达时唤醒监听接收线程
            serialPort.notifyOnDataAvailable(true);
            //设置当通信中断时唤醒中断线程
            serialPort.notifyOnBreakInterrupt(true);

            SerialPortChannel serialPortChannel = new SerialPortChannel(serialPort);
            serialPortChannel.addEventListener(new SerialPortEventListener() {
                @Override
                public void serialEvent(SerialPortEvent ev) {
                    if (SerialPortEvent.DATA_AVAILABLE == ev.getEventType()) {
                        byte[] msg = serialPortChannel.read();
                        threadPool.submit(new Runnable() {
                            @Override
                            public void run() {
                                serialPortInitializer.pipeline(msg);
                            }
                        });
                    }
                }
            });
            serialPortInitializer.initSerialPort(serialPortChannel);
            return serialPortChannel;
        } catch (UnsupportedCommOperationException e) {
            logger.error("{}设置串口参数失败", serialPort.getName(), e);
        }
        return null;
    }

    /**
     * 关闭SerialPortBootstrap
     */
    public void shutdown() {
        if (null != threadPool) {
            threadPool.shutdown();
        }
    }


    public SerialPortBootstrap handler(SerialPortInitializer serialPortInitializer) {
        this.serialPortInitializer = serialPortInitializer;
        return this;
    }

    public SerialPortBootstrap timeout(int timeout) {
        this.timeout = timeout;
        return this;
    }

    public SerialPortBootstrap threadPool(ExecutorService executorService) {
        this.threadPool = executorService;
        return this;
    }


    /**
     * 检查打开串口必备参数
     *
     * @return
     */
    private boolean check() {
        if (null == serialPortInitializer) {
            throw new IllegalParameterException("未配置SerialPortInitializer");
        }
        if (null == threadPool) {
            throw new IllegalParameterException("未配置threadPool");
        }
        return true;
    }


    /**
     * 设置默认工作线程池
     *
     * @return
     */
    private ExecutorService defaultThreadPool() {
        return new ThreadPoolExecutor(1,
                2,
                60,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue());
    }
}
